﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using Tesla.Bounding;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;
using Tesla.Scene;

namespace Tesla.Util {
    /// <summary>
    /// Batching utility designed to render normal-tangent-binormals (otherwise known
    /// as a tangent basis) from a given Mesh. This is similar to <see cref="SpriteBatch"/>,
    /// where the data is collected per-frame into large batches and rendered in a single
    /// wrap-around dynamic vertex buffer to minimize overhead.
    /// </summary>
    public sealed class LineBatch {
        private const int MaxBatchSize = 4096;
        private VertexBuffer _vertexBuffer;
        private int _lineVbPos;
        private Material _material;
        private VertexPositionColor[] _vertices;

        private Color _baseColor;
        private Color _tipColor;

        private BoundingBox _measureBox;
        private float _scaleRatio;

        /// <summary>
        /// Gets or sets the scale ratio used to scale the line.
        /// </summary>
        public float ScaleRatio {
            get {
                return _scaleRatio;
            }
            set {
                _scaleRatio = value;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="LineBatch"/>. Lines require a Position and
        /// a VertexColor to be rendered.
        /// </summary>
        /// <param name="material">The material used to render the line batches.</param>
        /// <param name="baseColor">Color of the line's base.</param>
        /// <param name="tipColor">Color of the line's tip.</param>
        public LineBatch(Material material, Color baseColor, Color tipColor) {
            _material = material;
            _material.ClearLogic();
            _baseColor = baseColor;
            _tipColor = tipColor;
            _vertexBuffer = new VertexBuffer(VertexPositionColor.VertexDeclaration, MaxBatchSize * 2, ResourceUsage.Dynamic);
            _lineVbPos = 0;
            _vertices = new VertexPositionColor[MaxBatchSize * 2];
            _scaleRatio = 1.0f;
            _measureBox = new BoundingBox();
        }

        /// <summary>
        /// Draws a portion of the mesh's tangent basis.
        /// </summary>
        /// <param name="mesh">Mesh to accumulate data from</param>
        /// <param name="renderer">The renderer</param>
        /// <param name="basisIndex">Specifies which lines to render: 0 for normals, 1 for tangents, 2 for binormals.</param>
        public void Draw(Mesh mesh, IRenderer renderer, int basisIndex) {
            //Check if we have the proper data
            MeshData md = mesh.MeshData;
            if(md == null) {
                return;
            }
            DataBuffer<Vector3> positions = md.Positions;
            DataBuffer<Vector3> normals = null;
            switch(basisIndex) {
                case 0:
                    normals = md.Normals;
                    break;
                case 1:
                    normals = md.Tangents;
                    break;
                case 2:
                    normals = md.Binormals;
                    break;
            }
            if(positions == null || normals == null) {
                return;
            }

            bool useIndices = md.UseIndexedPrimitives;
            bool useShortIndices = md.UsingShortIndices;
            DataBuffer indices = md.Indices;

            //Setup scaling
            float scale = 1.0f;
            if(mesh.WorldBounding != null) {
                _measureBox.Set(mesh.WorldBounding);
                scale = _scaleRatio * (_measureBox.Extents.Length() / 6);
            } else {
                scale *= 5f;
            }

            //Apply material
            _material.ApplyMaterial(renderer, mesh);
            renderer.SetVertexBuffer(_vertexBuffer);

            //Setup batches + render
            int numBatches = (useIndices) ? md.IndexCount : md.VertexCount;
            int index = 0;
            while(numBatches > 0) {
                DataWriteOptions options = DataWriteOptions.NoOverwrite;
                int actCount = numBatches;
                //Check if we need to split up
                if(actCount > MaxBatchSize - _lineVbPos) {
                    //Need to split up, so get the available space
                    actCount = MaxBatchSize - _lineVbPos;
                    //Need to check if we've reached the end of the VB, if so
                    //must have write discard to not cause a pipeline stall
                    if(_lineVbPos % MaxBatchSize == 0) {
                        options = DataWriteOptions.Discard;
                        _lineVbPos = 0;
                        //Reset actual count to the maximum space available, either
                        //requested batch number, or max batch size - whichever is smallest
                        actCount = System.Math.Min(MaxBatchSize, numBatches);
                    }
                }

                if(useIndices) {
                    if(useShortIndices) {
                        DataBuffer<short> shortIndices = (DataBuffer<short>) indices;
                        //Loop through mesh vertices and create the geometry
                        for(int i = index, j = 0; i < index + actCount; i++) {
                            Vector3 position = positions.Get(shortIndices.Get(i));
                            Vector3 normal = normals.Get(shortIndices.Get(i));
                            Vector3 position2;
                            Vector3.Multiply(ref normal, scale, out position2);
                            Vector3.Add(ref position, ref position2, out position2);

                            _vertices[j++] = new VertexPositionColor(position, _baseColor);
                            _vertices[j++] = new VertexPositionColor(position2, _tipColor);
                        }
                    } else {
                        DataBuffer<int> intIndices = (DataBuffer<int>) indices;
                        //Loop through mesh vertices and create the geometry
                        for(int i = index, j = 0; i < index + actCount; i++) {
                            Vector3 position = positions.Get(intIndices.Get(i));
                            Vector3 normal = normals.Get(intIndices.Get(i));
                            Vector3 position2;
                            Vector3.Multiply(ref normal, scale, out position2);
                            Vector3.Add(ref position, ref position2, out position2);

                            _vertices[j++] = new VertexPositionColor(position, _baseColor);
                            _vertices[j++] = new VertexPositionColor(position2, _tipColor);
                        }
                    }
                } else {
                    //Loop through mesh vertices and create the geometry
                    for(int i = index, j = 0; i < index + actCount; i++) {
                        Vector3 position = positions.Get(i);
                        Vector3 normal = normals.Get(i);
                        Vector3 position2;
                        Vector3.Multiply(ref normal, scale, out position2);
                        Vector3.Add(ref position, ref position2, out position2);

                        _vertices[j++] = new VertexPositionColor(position, _baseColor);
                        _vertices[j++] = new VertexPositionColor(position2, _tipColor);
                    }
                }

                //Write to VB
                int stride = VertexPositionColor.SizeInBytes;
                int offset = _lineVbPos * stride * 2;
                _vertexBuffer.SetData<VertexPositionColor>(_vertices, 0, actCount * 2, offset, stride, options);

                //Render
                for(int i = 0; i < _material.PassCount; i++) {
                    _material.ApplyPass(renderer, i);
                    renderer.Draw(PrimitiveType.LineList, actCount * 2, _lineVbPos * 2);
                }

                //Increment/decrement and prepare for the next iteration
                index += actCount;
                _lineVbPos += actCount;
                numBatches -= actCount;
            }
        }
    }
}
